
package com.x52im.rbchat.logic.chat_root.sendshortvideo;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;

import com.eva.android.BitmapHelper;
import com.eva.android.ToolKits;
import com.eva.android.widget.DataLoadingAsyncTask;
import com.eva.android.widget.WidgetUtils;
import com.eva.android.widget.alert.AlertDialog;
import com.eva.epc.common.file.FileHelper;
import com.eva.epc.common.util.CommonUtils;
import com.x52im.rbchat.Const;
import com.x52im.rbchat.MyApplication;
import com.x52im.rbchat.R;
import com.x52im.rbchat.logic.chat_guest.utils.TChatDataHelper;
import com.x52im.rbchat.logic.chat_guest.utils.TMessageHelper;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.logic.chat_root.model.FileMeta;
import com.x52im.rbchat.logic.chat_root.model.MessageExt;
import com.x52im.rbchat.logic.chat_root.sendimg.PreviewAndSendActivity;
import com.x52im.rbchat.logic.chat_friend.utils.ChatDataHelper;
import com.x52im.rbchat.logic.chat_friend.utils.MessageHelper;
import com.x52im.rbchat.logic.chat_group.utils.GChatDataHelper;
import com.x52im.rbchat.logic.chat_group.utils.GMessageHelper;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadManager;

import net.x52im.mobileimsdk.server.protocal.Protocal;

import java.io.File;
import java.util.Observable;
import java.util.Observer;

/**
 * 短视频消息发送的真正实现类.
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @since 4.4
 */
public class SendShortVideoProcessor {

    private final static String TAG = SendShortVideoProcessor.class.getSimpleName();

    private Context context = null;

    /** 用于一对聊天时表示消息接收者的uid、用于群聊时本字段表示文件接收的目标群id */
    private String toId = null;
    /**
     * 当前仅用于一对陌生人聊天时表示消息接收者的昵称.
     * @since 5.1 */
    private String toName = null;
    /** 功能应用场景:如用于陌生人聊天、一对一好友聊、世界频道聊等.
     * @see {@link PreviewAndSendActivity} */
    private String usedFor = "";

    // 刚录制完成时的临时路径
    private String originalPath = null;
    // 录制的视频时长
    private long originalDuration = 0;
    // 此视频是否是以最长录制时间完成的
    private boolean reachedMaxRecordTime = false;

    // 发送时是否需要显示一个确认对话框
    private boolean needConfirm = true;

    public SendShortVideoProcessor(Context context, String usedFor, String toId, String toName
            , String originalPath, long originalDuration, boolean reachedMaxRecordTime) {
        this(context, usedFor, toId, toName, originalPath, originalDuration, reachedMaxRecordTime, true);
    }

    public SendShortVideoProcessor(Context context, String usedFor, String toId, String toName
            , String originalPath, long originalDuration, boolean reachedMaxRecordTime, boolean needConfirm) {
        this.context = context;
        this.usedFor = usedFor;
        this.toId = toId;
        this.toName = toName;

        this.originalPath = originalPath;
        this.originalDuration = originalDuration;
        this.reachedMaxRecordTime = reachedMaxRecordTime;
        this.needConfirm = needConfirm;
    }

    /**
     * 短视频发送.
     */
    public void doSend() {
        if (originalPath != null) {
            File f = new File(originalPath);
            if (f.exists() && !f.isDirectory()) {
                long fsize = f.length();

                String hint = "";
                String fsizeHuman = CommonUtils.getConvenientFileSize(fsize, 2);
                if (this.reachedMaxRecordTime) {
                    hint = "当前录制已达最大允许的" + (Const.SHORT_VIDEO_RECORD_MAX_TIME / 1000) + "秒时长（视频文件大小为：" + fsizeHuman + "），是否发送该短视频消息？";
                } else {
                    hint = "视频文件大小为：" + fsizeHuman + "，是否发送该短视频消息？";
                }

                // 需要显示一个确认对话框
                if(needConfirm) {
                    // 发送请的提示框
                    new AlertDialog.Builder(context)
                            .setTitle(context.getResources().getString(R.string.short_video_recording_complete_alert_title))
                            .setMessage(hint)
                            // 用户同意发送此条短视频了
                            .setPositiveButton(context.getResources().getString(R.string.general_ok), (dialog, which) -> doSendImpl())
                            // 如果是取消发送就删除这个临时视频
                            .setNeutralButton(context.getResources().getString(R.string.general_cancel), (dialog, which) -> FileHelper.deleteFile(originalPath))
                            .setCancelable(false)
                            .show();
                }
                // 不需要确认，直接发送（这种情况目前是从大文件功能中，选择视频文件发送时）
                else {
                    doSendImpl();
                }
            } else {
                WidgetUtils.showWithDialog(context, "友情提示", "要发送的短视频不存在，请稍后再试！");
                return;
            }
        } else {
            WidgetUtils.showWithDialog(context, "友情提示", "无效的参数，短视频发送没有继续！");
            return;
        }
    }

    /**
     * 短视频发送.
     */
    private void doSendImpl() {
        boolean beforeSendOK = beforeSend();

        if(beforeSendOK) {
            // 计算MD5码是个耗时的过程，一个30M左右的文件，在千元Android机上可能需要2秒左右
            // ，200M左右的大文件耗时可能超过1分钟，所以使用异步线程来执行可以提升用户体验
            new DataLoadingAsyncTask<String, Integer, String>(context, "视频准备中，请稍候..") {
                @Override
                protected String doInBackground(String... params) {
                    try {
                        // 计算MD5码（本方法可保证在计算大至数G的文件时，也不会发生OOM）
                        return ToolKits.getFileMD5(originalPath);
                    } catch (Exception e) {
                        Log.w(TAG, e);
                        return null;
                    }
                }

                protected void onPostExecuteImpl(Object result) {
                    if (result != null && result instanceof String) {
                        final String fileMD5 = (String) result;

                        // 使用文件的MD5码重命名文件
                        File fileAfterRename = renameUseMD5(new File(originalPath), fileMD5, originalDuration);
                        if (fileAfterRename != null) {
                            Log.d(TAG, "【SendShortVideo】要发送的短视频文件重命名完成.");
                        } else {
                            Log.e(TAG, "【SendShortVideo】要发送的短视频重命名失败！");
                            WidgetUtils.showToast(context, "短视频文件重命名失败，请检查SD卡，发送已被取消！", WidgetUtils.ToastType.INFO);
                            return;
                        }

                        final String fileName = fileAfterRename.getName();
                        final long fileLength = fileAfterRename.length();
                        String filePath = fileAfterRename.getAbsolutePath();

                        // 检查一下此md5码的文件是否正在上传中，如果真在上传中就不允许本次再发了（强行再次上传会打乱
                        // 上个上传的断点算法和逻辑，而且从实际意义来说同一个文件完全没必要重复上传，等上一次发的传完再发不迟！）
                        BigFileUploadManager um = MyApplication.getInstance2().getIMClientManager().getBigFileUploadManager();
                        if (um != null) {
                            if (um.isUploading(fileMD5)) {
                                Log.d(TAG, "【短视频上传】要上传的短视频：" + filePath + "， 已存在相同的上传任务，本次任务没有继续！");
                                WidgetUtils.showToast(context, "文件" + fileName + "已经在发送中，无需重复发送！"
                                        , WidgetUtils.ToastType.INFO);
                                return;
                            }
                        }


                        //** 为视频生成首帧预览图并缓存到本地
                        try {
                            Bitmap firstFrame = com.eva.android.ToolKits.getFirstFrameForVideo(filePath);
                            if (firstFrame != null) {

                                // 计算首帧图片的尺寸压缩比例（等比缩放）
                                int maxWidth = WidgetUtils.dip2px(MyApplication.getInstance2(), 160);
                                int maxHeight = WidgetUtils.dip2px(MyApplication.getInstance2(), 160);
                                float decreaseToPercent = BitmapHelper.calculateBitmapDecreasePercent(firstFrame, maxWidth, maxHeight);
                                if(decreaseToPercent >= 1.0f) {
                                    // 20190605 ：为了更好的适应高dpi手机，默认缩放比例已由原0.4f改为0.6f
                                    decreaseToPercent = 0.6f;
                                }

                                Log.d(TAG, "【短视频首帧图缩小比例计算完成】decreaseToPercent=> "+decreaseToPercent);

                                // 将首帧图片进行尺寸压缩（太大的话，界面上看起来很丑）
                                firstFrame = BitmapHelper.decreaseBitmapSize(firstFrame , decreaseToPercent, true);

                                // 如果尺寸压缩成功则继续处理
                                if (firstFrame != null) {
                                    // 视频首帧预览图的文件名（本地保存的名）
                                    final String imgtLocalSavedName = ReceivedShortVideoHelper.constructShortVideoThumbName_localSaved(fileName);
                                    // 视频首帧预览图将要保存的完整路径
                                    final File imgFile = new File(ReceivedShortVideoHelper.getReceivedShortVideoPreviewSavedDirHasSlash() + imgtLocalSavedName);

                                    // 如果要保存的目录不存在，则先创建之
                                    if (!imgFile.getParentFile().exists())
                                        imgFile.getParentFile().mkdirs();

                                    // 将首帧图保存到文件（图片保存质量设为75左右时，在图片品质几乎不变的情况下，文件大小可减小为原先的10分之1）
                                    boolean imgSaveSucess = BitmapHelper.saveBitmapToFile(firstFrame, 75, imgFile);

                                    if (imgSaveSucess)
                                        Log.i(TAG, "【短视频上传-首帧预览图生成】首帧保存文件成功【OK】（位置：" + imgFile.getAbsolutePath() + "）");
                                } else {
                                    Log.w(TAG, "【短视频上传-首帧预览图生成】视频" + filePath + "的首帧图片尺寸压缩失败了，结果为null，本次首帧处理提前结束！");
                                }
                            } else {
                                Log.w(TAG, "【短视频上传-首帧预览图生成】视频" + filePath + "的首帧预览图保存失败！");
                            }
                        } catch (OutOfMemoryError oom) {
                            Log.w(TAG, "【短视频上传-首帧预览图生成】视频" + filePath + "的首帧图片尺寸压缩时OOM了", oom);
                        } catch (Exception e) {
                            Log.w(TAG, "【短视频上传-首帧预览图生成】视频" + filePath + "的首帧生成过程中出错了，原因：" + e.getMessage(), e);
                        }

                        //** 开始执行短视频文件上传的全部逻辑
                        SendShortVideoProcessor.doSendLogic(context, false, toId, toName, usedFor, filePath, fileMD5);
                    } else {
                        WidgetUtils.showToast(context, "短视频的MD5码计算失败，发送已被取消！", WidgetUtils.ToastType.WARN);
                    }
                }
            }.execute();
        }
    }

    /**
     * 真正的短视频文件发送完整逻辑的方法。
     *
     * @param context Context
     * @param forForward true表示用于消息转发功能时，否则表示是正常的文件发送
     * @param toId 接收者（如果是一对一聊天则本参数是对方的uid，如果是世界频道或群聊则本参数为gid）
     * @param toName 接收者名字（本字段当前仅用于陌生人聊天时，其它聊天类型时请传null即可）
     * @param usedFor 用途
     * @param filePath 文件完整路径
     * @param fileMD5 文件md5码
     * @param sucessObsExtra 消息发送完成后，额外要做的事
     */
    public static void doSendLogic(final Context context, boolean forForward, final String toId, final String toName
            , final String usedFor, final String filePath, final String fileMD5) {
        doSendLogic(context, forForward, toId, toName, usedFor, filePath, fileMD5, null);
    }

    /**
     * 真正的短视频文件发送完整逻辑的方法。
     *
     * @param context Context
     * @param forForward true表示用于消息转发功能时，否则表示是正常的文件发送
     * @param toId 接收者（如果是一对一聊天则本参数是对方的uid，如果是世界频道或群聊则本参数为gid）
     * @param toName 接收者名字（本字段当前仅用于陌生人聊天时，其它聊天类型时请传null即可）
     * @param usedFor 用途
     * @param filePath 文件完整路径
     * @param fileMD5 文件md5码
     * @param sucessObsExtra 消息发送完成后，额外要做的事
     */
    public static void doSendLogic(final Context context, boolean forForward, final String toId, final String toName
            , final String usedFor, final String filePath, final String fileMD5, final Observer sucessObsExtra) {
        File f = new File(filePath);

        final String fileName = f.getName();
        final long fileLength = f.length();

        //** 将短视频消息先插入到本地聊天界面中（准确地说是数据模型中）
        Message cme = null;
        // 先生成指纹码
        final String fingerPring = Protocal.genFingerPrint();
        FileMeta fileMeta = new FileMeta(fileName, fileMD5, fileLength, filePath);
        // 用于正式聊天的短视频消息发送
        if (Const.CHAT_TYPE_FREIDN$CHAT.equals(usedFor)) {
            cme = ChatDataHelper.addChatMessageData_outgoing(context, toId, MessageExt.createChatMsgEntity_OUTGO_SHORTVIDEO(fileMeta, fingerPring));
        }
        // 用于陌生人聊天的短视频消息发送
        else if (Const.CHAT_TYPE_GUEST$CHAT.equals(usedFor)){
            cme = TChatDataHelper.addChatMessageData_outgoing(context, toId, MessageExt.createChatMsgEntity_OUTGO_SHORTVIDEO(fileMeta, fingerPring));
        }
        // 用于群聊时
        else if (Const.CHAT_TYPE_GROUP$CHAT.equals(usedFor)) {
            cme = GChatDataHelper.addChatMessageData_outgoing(context, toId, MessageExt.createChatMsgEntity_OUTGO_SHORTVIDEO(fileMeta, fingerPring));
        }


        //** 观察者：用于上传完成时通知本方法的调用者来做余下的事（把这个观察者当回调来理解就好了）
        Observer observerForFileUploadProcessOK = (observable, data) -> {
            final FileMeta fm = new FileMeta(fileName, fileMD5, fileLength
                    // 注意：本数据是要发送给对方的，所以此路径是对方收到后的保存绝对路径，且应该是由对方收到消息
                    // 后来决定（因为手机型号不同可能文件目录的规则不相同，比如老的android跟android 4.4以上就不同）
//                  , ReceivedFileHelper.getReceivedFileSavedDirHasSlash(context) + fileName
                    , null
            );

            // 用于正式聊天的文件发送
            if (Const.CHAT_TYPE_FREIDN$CHAT.equals(usedFor)){
                // 并发送一条短视频消息给好友（文件上传逻辑已在“SendShortVideoHelper.processBigFileUpload(..)”处理完）
                MessageHelper.sendShortVideoMessageAsync((Activity) context, toId, fm, fingerPring, sucessObsExtra);
            }
            // 用于陌生人聊天的文件发送
            else if (Const.CHAT_TYPE_GUEST$CHAT.equals(usedFor)){
                // 并发送一条短视频消息给陌生人（文件上传逻辑已在“SendShortVideoHelper.processBigFileUpload(..)”处理完）
                TMessageHelper.sendShortVideoMessageAsync((Activity) context, toId, toName, fm, fingerPring, sucessObsExtra);
            }
            // 用于群聊时
            else if (Const.CHAT_TYPE_GROUP$CHAT.equals(usedFor)) {
                // 并发送一条短视频消息给群友（文件上传逻辑已在“SendShortVideoHelper.processBigFileUpload(..)”处理完）
                GMessageHelper.sendShortVideoMessageAsync((Activity) context, toId, fm, fingerPring, sucessObsExtra);
            }
        };

        if(!forForward) {
            //** 开始处理短视频上传的完整逻辑
            SendShortVideoHelper.processShortVideoUpload(context, fileName, filePath, fileMD5, cme, observerForFileUploadProcessOK);
        }
        // 如果是用于消息转发功能就不需要走文件的处理逻辑，直接ui显示
        else {
            //** 以下设置没在 sucessObsExtra 观察者里做的原因：是大文件型的上传不同于普通消息，普通消息是等到网络指
            //** 令发出成合后才会将Message对象插入到ui界面等，而大文件型的消息是无论文件有没有处理完成、网络指令有没
            //** 发出，它都会先将Message对象插入到ui界面（先显示），然后再通过消息上的各种状态是告之用户此条文件型消息的状态是到什么程度了。
            // 转发的消息不需要处理文件上传，直接默认就认为文件已经上传好了（因为功能就只允许转发成功发出的文件消息）
            cme.setSendStatusSecondary(Message.SendStatusSecondary.processOk);
            // 设置标识，以便消息发送失败时，用户点击重传时进行特殊的逻辑处理
            cme.setForwardOutgoing(true);

            // 直接通知观察者刷新ui
            observerForFileUploadProcessOK.update(null, null);
        }
    }

    /**
     * 发送前的检查过程.
     *
     * @return true表示检查通过，否则不合法的短视频文件不应被发送
     */
    public boolean beforeSend() {
        boolean sucess = false;
        try {
            File f = new File(originalPath);
            // 文件是否存在的检查
            if (originalPath == null || !f.exists()) {
                Log.w(TAG, "【SendShortVideo】要发送的短视频文件" + originalPath + "不存在，本地发送没有继续！");
                WidgetUtils.showToast(context, "短视频文件不存在，发送已被取消！", WidgetUtils.ToastType.WARN);
                return false;
            }

            // 是否文件夹检查
            if (f.isDirectory()) {
                Log.w(TAG, "【SendShortVideo】要发送的" + originalPath + "不是文件，本地发送没有继续！");
                WidgetUtils.showToast(context, "不是文件，发送已被取消！", WidgetUtils.ToastType.WARN);
                return false;
            }

            // 允许发送的最大文件大小检查
            if (f.length() > Const.SEND_SHORT_VIDEO_DATA_MAX_LENGTH || f.length() <= 0) {
                Log.w(TAG, "【SendShortVideo】要发送的短视频文件" + originalPath + "大小非法，(MAX=" + Const.SEND_SHORT_VIDEO_DATA_MAX_LENGTH + "字节)，本地发送没有继续！");
                WidgetUtils.showWithDialog(context, "文件超限提示", "文件过大，当前允许最大发送" + CommonUtils.getConvenientFileSize(Const.SEND_SHORT_VIDEO_DATA_MAX_LENGTH, 0) + "的短视频，本次发送已取消！");
                return false;
            }

            // 允许发送的最小时长的检查
            if ((originalDuration / 1000) < 1) {
                Log.w(TAG, "【SendShortVideo】要发送的短视频文件" + originalPath + "时长太短duration=" + originalDuration + "毫秒(MIN=1秒)，本地发送没有继续！");
                WidgetUtils.showToast(context, "视频时间太短，发送已被取消！", WidgetUtils.ToastType.INFO);
                return false;
            }

            sucess = true;
        } catch (Exception e) {
            Log.e(TAG, "【SendShortVideo】短视频文件发送前的检查过程中出错了！", e);
            WidgetUtils.showToast(context, "短视频文件发送失败，请重试！", WidgetUtils.ToastType.INFO);
        }

        return sucess;
    }


    /**
     * 将临时视频文件重命名.
     *
     * @param tempFileSavedPath
     * @return
     */
    public static File renameUseMD5(File tempFileSavedPath, String fileMd5, long durationOfVideo) {
        File fileAfterRename = null;
        try {
            if (fileMd5 != null) {
                // 存放于数据库、本地缓存的文件名格式："时长_MD5码.mp4",形如:"120000_0b272fca28252641231a94f63d8e25fa.mp4"
                String fileNameUsedMd5 = ReceivedShortVideoHelper.constructShortVideoFileName(durationOfVideo, fileMd5);

                try {
                    fileAfterRename = new File(tempFileSavedPath.getParent() + "/" + fileNameUsedMd5);
                    tempFileSavedPath.renameTo(fileAfterRename);
                } catch (Exception e) {
                    Log.e(TAG, "【SendShortVideo】将临时文件" + tempFileSavedPath + "重命名失败了，上传将不能继续！", e);
                    // 如果重命名失败，就尝试把这个文件删除（没有必要留了）
                    try {
                        fileAfterRename.delete();
                        tempFileSavedPath.delete();
                    } catch (Exception e2) {
                    }
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "【SendShortVideo】重命名短视频文临时文件的逻辑中出错了，" + e.getMessage() + "，上传将不能继续！", e);
        }

        return fileAfterRename;
    }
}